home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2010 April
/
PCWorld0410.iso
/
pluginy Firefox
/
398
/
398.xpi
/
chrome
/
forecastfox.jar
/
content
/
parser
/
parser-service.js
< prev
next >
Wrap
Text File
|
2010-02-04
|
23KB
|
766 lines
/*------------------------------------------------------------------------------
Copyright (c) 2008 Ensolis, LLC. All Rights Reserved.
----------------------------------------------------------------------------*/
/******************************************************************************
* Get the selected forecastfox locale.
*
* @return The locale name.
******************************************************************************/
function getLocale()
{
var reg = Cc["@mozilla.org/chrome/chrome-registry;1"].
getService(Ci.nsIXULChromeRegistry);
return reg.getSelectedLocale("forecastfox");
}
/******************************************************************************
* Evaluate the data and return the correct data type.
*
* @param The data type.
* @param The value.
* @return The value in the correct javascript data type.
******************************************************************************/
function evalData(aType, aData)
{
var rv = "";
switch (aType) {
//integer data type
case "Int":
rv = (aData == null) ? 0 : Number(aData);
break;
//boolean data type
case "Bool":
rv = (aData == null) ? false : Boolean(Number(aData));
break;
//string data type
case "Char":
default:
rv = (aData == null) ? "" : String(aData);
break;
}
//return the data
return rv;
}
/******************************************************************************
* Interfaces used by a service for parsing the weather feed. Supplies a set
* items and methods used for manipulating the data. Targets are stored as a
* flat list with an id made up of the target and the index with path and group
* properties. Items are stored in a flat list in a group object. The id is
* made up of the group and item name. The group property on the target
* is used to retrieve the group object which has the items. Data is stored
* in a flat list of target, index, and item name.
*
* @status FROZEN
* @version 1.0
******************************************************************************/
function ParserService()
{
//setup a new error
this._error = Cc["@ensolis.com/forecastfox/error-item;1"].
createInstance(Ci.ffIErrorItem);
}
ParserService.prototype = {
__proto__: new ServiceBase("ParserService"),
_dskSvc: null,
_cnvSvc: null,
_prfSvc: null,
_file: null,
_items: null,
_data: null,
_hasData: null,
_resolver: null,
_locale: null,
////////////////////////////////
// ffIService
/**
* Initialize the component. Called by the manager service.
*/
start: function ParserService_start()
{
//setup the variables
this._items = {};
this._data = {};
this._hasData = false;
this._locale = getLocale();
//get the disk and conversion services
var mgrSvc = Cc["@ensolis.com/forecastfox/manager-service;1"].
getService(Ci.ffIManagerService);
this._dskSvc = mgrSvc.disk;
this._cnvSvc = mgrSvc.converters;
//get the profile service so the [prof] variable can use it
this._prfSvc = mgrSvc.profiles;
//get the xpath resolver
this._resolver = Cc["@ensolis.com/forecastfox/resolver-item;1"].
createInstance(Ci.nsIDOMXPathNSResolver);
//set the feed namespace
if (this._resolver instanceof Ci.ffIItem)
this._resolver.setProperty("adc", "http://www.accuweather.com");
//get the parser file
this._file = this._dskSvc.get("parser.js", TYPE_DEFAULTS);
//load the data
return this._loadParser();
},
/**
* Destroy the component. Called by the manager service. This may be
* called prior to start so it needs to be safe.
*/
stop: function ParserService_stop()
{
//clear variables
this._dskSvc = null;
this._cnvSvc = null;
this._prfSvc = null;
this._file = null;
this._items = null;
this._data = null;
this._hasData = null;
this._resolver = null;
this._locale = null;
},
////////////////////////////////
// ffIParserService
/**
* Last modified date of datasource.
*/
get lastModified() {
if (!this._file || !this._file.exists())
return 0;
else
return this._file.lastModifiedTime;
},
/**
* Parse the DOM document supplied by the feed. Returns false if
* parsing failed. Use lastError attribute to obtain more detailed
* error information.
*
* @param The DOM document to parse.
* @return False if parsing failed.
*/
parseDOM: function ParserService_parseDOM(aDocument)
{
//reset the data variable
this._data = {};
this._hasData = false;
//setup error variables
const PREFIX = "ff.parser.dom.";
var name = "";
var message = "";
//check that the parser is loaded
if (!this._items.hasOwnProperty("targets")) {
name = this.bundle.GetStringFromName(PREFIX + "loaded.name");
message = this.bundle.GetStringFromName(PREFIX + "loaded.message");
this._error.init(SEVERITY_ERROR, name, message);
return false;
}
//error if document is not well formed
if (!this._dskSvc.validate(aDocument, "adc_database")) {
name = this.bundle.GetStringFromName(PREFIX + "valid.name");
message = this.bundle.GetStringFromName(PREFIX + "valid.message");
this._error.init(SEVERITY_ERROR, name, message);
return false;
}
//check for error messages
var node = this._evalPath("./adc:failure", aDocument);
if (node) {
name = node.textContent;
message = node.textContent;
this._error.init(SEVERITY_ERROR, name, message);
return false;
}
//loop through the targets
for (var id in this._items.targets) {
var target = this._items.targets[id];
//get the items for the target and the context node
var items = this._items.groups[target.group];
node = this._evalPath(target.path, aDocument.documentElement);
//loop through the items and evaluate
for (var id2 in items) {
var id3 = target.name + "-" + String(target.index) + "-" + id2;
this._data[id3] = this._evalItem(items[id2], node);
}
}
//parse was successful
this._hasData = true;
return true;
},
/**
* Serialize the parsed document to javascript source. Used to cache
* the parser items. Use the parseCache function to read the cache.
* Returns false if serializing failed. Use lastError attribute
* to obtain more detailed error information.
*
* @param The file to serialize to.
* @return False if serialize failed.
*/
serializeCache: function ParserService_serializeCache(aCache)
{
//setup error variables
const PREFIX = "ff.parser.serialize.";
var name = this.bundle.GetStringFromName(PREFIX + "name");
var message = "";
//no data stored
if (!this._hasData) {
message = this.bundle.GetStringFromName(PREFIX + "empty.message");
this._error.init(SEVERITY_WARNING, name, message);
return false;
}
//cache file is not writable
if ((aCache.exists() && !aCache.isWritable()) ||
(!aCache.exists() && !aCache.parent.isWritable())) {
message = this.bundle.formatStringFromName(PREFIX + "write.message",
[aCache.path], 1);
this._error.init(SEVERITY_ERROR, name, message);
return false;
}
//convert the items to javascript source
var content = this._data.toSource();
//write to the cache
this._dskSvc.writeText(aCache, content, false, false, false);
return true;
},
/**
* Parse the cached parser items. Returns false if
* parsing failed. Use lastError attribute to obtain more detailed
* error information.
*
* @param The cache file.
* @return False if parsing failed.
*/
parseCache: function ParserService_parseCache(aCache)
{
//reset the data variable
this._data = {};
this._hasData = false;
//setup error variables
const PREFIX = "ff.parser.cache.";
var name = "";
var message = "";
//check that the cache file exists
if (!aCache.exists()) {
name = this.bundle.GetStringFromName(PREFIX + "exists.name");
message = this.bundle.GetStringFromName(PREFIX + "exists.message");
this._error.init(SEVERITY_ERROR, name, message);
return false;
}
//check that the cache file is readable
if (!aCache.isReadable()) {
name = this.bundle.GetStringFromName(PREFIX + "read.name");
message = this.bundle.formatStringFromName(PREFIX + "read.message",
[aCache.path], 1);
this._error.init(SEVERITY_ERROR, name, message);
return false;
}
//read the cache
var content = this._dskSvc.readText(aCache);
if (!content) {
name = this.bundle.GetStringFromName(PREFIX + "content.name");
message = this.bundle.GetStringFromName(PREFIX + "content.message");
this._error.init(SEVERITY_ERROR, name, message);
return false;
}
/** check that the cache is older than the parser. This needs to be
done after reading the cache because there could have been
outstanding file writes which the read will force to occur. **/
if (this.lastModified > aCache.lastModifiedTime) {
name = this.bundle.GetStringFromName(PREFIX + "last.name");
message = this.bundle.GetStringFromName(PREFIX + "last.message");
this._error.init(SEVERITY_ERROR, name, message);
return false;
}
//set items
try {
this._data = eval(content);
} catch(e) {
this._data = {};
this._error.init(SEVERITY_ERROR, e.name, e.message);
return false;
}
//parse was successful
this._hasData = true;
return true;
},
/**
* Parser has a specific item. Use the target, index, and item name
* separated by hyphens as the id of the item.
*
* @param ID of the parser item.
* @return True if item exists.
*/
hasItem: function ParserService_hasItem(aID)
{
//split the id into its parts
var unique = aID.split("-");
//check that we have the target
var id = unique[0] + "-" + unique[1];
if (!this._items.targets.hasOwnProperty(id))
return false;
//check actual item
var target = this._items.targets[id];
var items = this._items.groups[target.group];
if (items.hasOwnProperty(unique[2]))
return true;
//check global items
target = this._items.targets["global-0"];
items = this._items.groups[target.group];
return items.hasOwnProperty(unique[2]);
},
/**
* Retrieve a parser item returns null if item doesn't exist.
* Use the target, index, and item name separated by hyphens
* as the id of the item.
*
* @param ID of the parser item.
* @return A ffIParserItem.
*/
getItem: function ParserService_getItem(aID)
{
//split the id into its parts
var unique = aID.split("-");
//check that we have the target
var id = unique[0] + "-" + unique[1];
if (!this._items.targets.hasOwnProperty(id))
return null;
//check actual item
var target = this._items.targets[id];
var items = this._items.groups[target.group];
if (items.hasOwnProperty(unique[2]))
return this._createItem(items[unique[2]], target);
//check global items
target = this._items.targets["global-0"];
items = this._items.groups[target.group];
if (items.hasOwnProperty(unique[2]))
return this._createItem(items[unique[2]], target);
//item was not found
return null;
},
/**
* Retrieve an array of parser items.
*
* @param Target of the item.
* @param Index of the item.
* @param Count of items in the array.
* @return An array of ffIParserItem components.
*/
getItems: function ParserService_getItems(aTarget, aIndex, aCount)
{
//do globals first
var rv = [];
var target = this._items.targets["global-0"];
var items = this._items.groups[target.group];
//loop through the items
for (var id in items) {
var item = items[id];
//skip hidden items
if (item.hasOwnProperty("hidden"))
continue;
//add the items
var id2 = target.name + "-" + String(target.index) + "-" + id;
rv.push(this.getItem(id2));
}
//check the specific target
id = aTarget + "-" + String(aIndex);
if (!this._items.targets.hasOwnProperty(id) || aTarget == "global") {
aCount.value = rv.length;
return rv;
}
//get the target data
target = this._items.targets[id];
items = this._items.groups[target.group];
//loop through the items
for (id in items) {
item = items[id];
//skip hidden items
if (item.hasOwnProperty("hidden"))
continue;
//add the items
id2 = target.name + "-" + String(target.index) + "-" + id;
rv.push(this.getItem(id2));
}
//return the array
aCount.value = rv.length;
return rv;
},
/**
* Retrieve the value of a parser item. Pass in a
* converter name to use in the conversion process
* or null for the default value.
*
* @param Target of the item.
* @param Index of the item.
* @param Name of the item.
* @param Name of the converter or null.
* @return The formatted value of the item.
*/
getValue: function ParserService_getValue(aTarget, aIndex, aName, aConverter)
{
var item = null;
//check that we have the target
var id = aTarget + "-" + String(aIndex);
if (!this._items.targets.hasOwnProperty(id))
return null;
//check actual item
var target = this._items.targets[id];
var items = this._items.groups[target.group];
if (items.hasOwnProperty(aName))
item = items[aName];
else {
//check global items
target = this._items.targets["global-0"];
items = this._items.groups[target.group];
if (items.hasOwnProperty(aName))
item = items[aName];
}
//item was not found
if (!item)
return null;
//return an N/A if data doesn't exist
id = target.name + "-" + String(target.index) + "-" + item.name;
if (!this._data.hasOwnProperty(id))
return "N/A";
//no data so return the value
var value = this._data[id];
if (value == "N/A")
return value;
/**
* calculations are always ran first to get the correct
* javascript context.
*/
var comp = this;
if (item.hasOwnProperty("calc")) {
var calc = item.calc;
calc = calc.replace(/\$VAL/g, "'" + String(value) + "'");
try {
value = eval(calc);
} catch(e) {}
}
/** if we are being called recursively return early so we do not convert
the same value multiple times. **/
if (Components.stack.caller.name == "ParserService_getValue")
return value;
//use the conversion service
if (item.hasOwnProperty("conversion"))
value = this._cnvSvc.formatValue(item.conversion, aConverter, value);
//return the final value
return value;
},
/**
* Builds a label from the passed in string. If the label has a "+" within
* a parser name it will be split and the remaining will be used to retrieve
* the converter to use for that value. This allows multiple uom to be
* displayed for an item.
*
* @param The label string to format.
* @param Target of the item or null.
* @param Index of the item or null.
* @return The formatted label.
*/
getLabel: function ParserService_getLabel(aLabel, aTarget, aIndex)
{
//nothing has been parsed
if (!this._hasData)
return "";
//function used to replace
var comp = this;
function getLabel_replace(aContent) {
//get the string text
var text = aContent.substring(1, aContent.length -1);
text = text.split("+");
//get the item and converter names
var name = text[0];
var converter = null;
if (text.length > 1)
converter = text[1];
//create the item id and see if we have it
var value = comp.getValue(aTarget, aIndex, name, converter);
if (value != null)
return value;
else
return text;
}
//do the replace
var label = aLabel;
try {
label = aLabel.replace(/\[[^\[\]]+\]/g, getLabel_replace);
} catch (e) {}
//return the formatted label
getLabel_replace = null;
return label;
},
////////////////////////////////
// Internal Functions
/**
* Evaluate an xpath expression. Use the node as the context to
* evaluate the expression in.
*
* @param Expression to evaluate.
* @param Context node.
* @return A DOM node result.
*/
_evalPath: function ParserService__evalPath(aExpr, aNode)
{
//get the document
var doc;
try {
doc = (aNode.ownerDocument == null) ? aNode : aNode.ownerDocument;
} catch(e) {
this._dskSvc.log("Error evaluating xpath.", e, null);
return null;
}
//evaluate the expression
var result = null;
try {
var results = doc.evaluate(aExpr, aNode, this._resolver,
Ci.nsIDOMXPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
result = results.snapshotItem(0);
} catch(e) {}
//return the variable
return result;
},
/**
* Evaluate an item to get the data from the feed.
*
* @param The item to evaluate.
* @param The context node.
* @return The evaluated data.
*/
_evalItem: function ParserService__evalItem(aItem, aContext)
{
//get the data type
var type = aItem.type;
//no path so return empty data
if (!aItem.hasOwnProperty("path"))
return evalData(type, null);
else
var path = aItem.path;
//process the prepath
var node = null;
if (aItem.hasOwnProperty("prepath")) {
node = this._evalPath(aItem.prepath, aContext);
if (!node)
path = path.replace(/\$PRE/g, "''");
else
path = path.replace(/\$PRE/g, node.textContent);
} else
path = path.replace(/\$PRE/g, "''");
//process the path
node = this._evalPath(path, aContext);
if (!node)
return evalData(type, null);
//return the value
return (node.textContent == "N/A") ? "N/A" :
evalData(type, node.textContent);
},
/**
* Translate given feed data in to localized values.
*
* @param Target of the item to translate.
* @param Index of the item to translate.
* @param Name of the item to translate.
* @param Converter name for getValue call.
* @return The localized version of the feed data.
*/
_translate: function ParserService__translate(aTarget, aIndex, aName, aConverter)
{
var value = null;
const PREFIX = "ff.parser.";
//translate forecast based on icon
if (aName == "t" || aName == "tlong") {
//if we are using an English locale, then just use the feed's data.
if (this._locale.match("en-"))
return this.getValue(aTarget, aIndex, aName + "_en", aConverter);
//international version... so get the icon and then return the corresponding string.
value = this.getValue(aTarget, aIndex, "icon", aConverter);
return this.bundle.GetStringFromName(PREFIX + "forecast." + value);
}
///translate all others based on the code value
value = this.getValue(aTarget, aIndex, aName + "code", aConverter);
//change to lower case and replace space with underscore
value = value.toLowerCase().replace(/\//g, "");
value = value.replace(/\s/g, "_");
//get the translation
try {
value = this.bundle.GetStringFromName(PREFIX + aName + "." + value);
} catch(e) {}
//return the value
return value;
},
/**
* Create a parser item from a javascript object
* representing that item.
*
* @param The javascript representation of the item.
* @param The target of the item.
* @return The parser item.
*/
_createItem: function ParserService__createItem(aItem, aTarget)
{
//create an empty parser item
var item = Cc["@ensolis.com/forecastfox/parser-item;1"].
createInstance(Ci.ffIParserItem);
//loop through the properties
for (var property in aItem)
item.setProperty(property, aItem[property]);
//set the target based properties
item.setProperty("target", aTarget.name);
item.setProperty("index", aTarget.index);
var id = item.target + "-" + String(item.index) + "-" + item.name;
item.setProperty("ID", id);
//get description
var alias = item.getProperty("alias");
alias = (alias != null) ? alias : item.name;
var description = item.name;
try {
description = this.bundle.GetStringFromName("ff.parser." + alias);
} catch(e) {}
item.setProperty("description", description);
//return the item
return item;
},
/**
* Load the parser data from file.
*
* @return False if the load fails.
*/
_loadParser: function ParserService__loadParser()
{
//setup error variables
const PREFIX = "ff.parser.load.";
var name = this.bundle.GetStringFromName(PREFIX + "name");
var message = "";
//file doesn't exist
if (!this._file.exists()) {
message = this.bundle.GetStringFromName(PREFIX + "exists.message");
this._error.init(SEVERITY_ERROR, name, message);
return false;
}
//file is not readable
if (!this._file.isReadable()) {
message = this.bundle.formatStringFromName(PREFIX + "read.message",
[this._file.path], 1);
this._error.init(SEVERITY_ERROR, name, message);
return false;
}
//get the content of the file
var content = this._dskSvc.readText(this._file);
if (!content) {
message = this.bundle.GetStringFromName(PREFIX + "empty.message");
this._error.init(SEVERITY_ERROR, name, message);
return false;
}
//save the data in the items variable
try {
this._items = eval(content);
} catch(e) {
this._items = {};
this._error.init(SEVERITY_ERROR, name, e.message);
return false;
}
//loaded successfully
return true;
}
};